1 /* 2 * Hunt - a framework for web and console application based on Collie using Dlang development 3 * 4 * Copyright (C) 2015-2017 Shanghai Putao Technology Co., Ltd 5 * 6 * Developer: HuntLabs 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.application.application; 13 14 import collie.codec.http.server.websocket; 15 import kiss.container.ByteBuffer; 16 import collie.codec.http.server; 17 import collie.codec.http; 18 import collie.bootstrap.serversslconfig; 19 import collie.utils.exception; 20 import hunt.cache; 21 22 public import kiss.event; 23 public import kiss.event.EventLoopGroup; 24 25 public import std.socket; 26 public import kiss.logger; 27 public import std.file; 28 29 import std.string; 30 import std.conv; 31 import std.stdio; 32 import std.uni; 33 import std.path; 34 import std.parallelism; 35 import std.exception; 36 37 import hunt.init; 38 import hunt.routing; 39 import hunt.application.dispatcher; 40 import hunt.security.acl.Manager; 41 42 public import hunt.http; 43 public import hunt.view; 44 public import hunt.i18n; 45 public import hunt.application.config; 46 public import hunt.application.middleware; 47 public import hunt.security.acl.Identity; 48 49 50 abstract class WebSocketFactory 51 { 52 IWebSocket newWebSocket(const HTTPMessage header); 53 }; 54 55 56 final class Application 57 { 58 static @property Application getInstance() 59 { 60 if(_app is null) 61 { 62 _app = new Application(); 63 } 64 65 return _app; 66 } 67 68 Address binded(){return addr;} 69 70 /** 71 Add a Router rule 72 Params: 73 method = the HTTP method. 74 path = the request path. 75 handle = the delegate that handle the request. 76 group = the rule's domain group. 77 */ 78 auto addRoute(string method, string path, HandleFunction handle, string group = DEFAULT_ROUTE_GROUP) 79 { 80 logDebug(__FUNCTION__,method, path, handle, group); 81 this._dispatcher.router.addRoute(method, path, handle, group); 82 83 return this; 84 } 85 86 Application GET(string path,HandleFunction handle) 87 { 88 this._dispatcher.router.addRoute("GET", path, handle,DEFAULT_ROUTE_GROUP); 89 return this; 90 } 91 Application POST(string path,HandleFunction handle) 92 { 93 this._dispatcher.router.addRoute("POST", path, handle,DEFAULT_ROUTE_GROUP); 94 return this; 95 } 96 Application DELETE(string path,HandleFunction handle) 97 { 98 this._dispatcher.router.addRoute("DELETE", path, handle,DEFAULT_ROUTE_GROUP); 99 return this; 100 } 101 Application PATCH(string path,HandleFunction handle) 102 { 103 this._dispatcher.router.addRoute("PATCH", path, handle,DEFAULT_ROUTE_GROUP); 104 return this; 105 } 106 Application PUT(string path,HandleFunction handle) 107 { 108 this._dispatcher.router.addRoute("PUT", path, handle,DEFAULT_ROUTE_GROUP); 109 return this; 110 } 111 Application HEAD(string path,HandleFunction handle) 112 { 113 this._dispatcher.router.addRoute("HEAD", path, handle,DEFAULT_ROUTE_GROUP); 114 return this; 115 } 116 Application OPTIONS(string path,HandleFunction handle) 117 { 118 this._dispatcher.router.addRoute("OPTIONS", path, handle,DEFAULT_ROUTE_GROUP); 119 return this; 120 } 121 122 // enable i18n 123 Application enableLocale(string resPath = DEFAULT_LANGUAGE_PATH, string defaultLocale = "en-us") 124 { 125 I18n i18n = I18n.instance(); 126 127 i18n.loadLangResources(resPath); 128 i18n.defaultLocale = defaultLocale; 129 130 return this; 131 } 132 133 void setWebSocketFactory(WebSocketFactory webfactory) 134 { 135 _wfactory = webfactory; 136 } 137 138 version(NO_TASKPOOL){} else { 139 @property TaskPool taskPool(){return _tpool;} 140 } 141 142 /// get the router. 143 @property router() 144 { 145 return this._dispatcher.router(); 146 } 147 148 @property server(){return _server;} 149 150 @property mainLoop(){return _server.eventLoop;} 151 152 @property loopGroup(){return _server.group;} 153 154 @property AppConfig appConfig(){return Config.app;} 155 156 void setCreateBuffer(CreatorBuffer cbuffer) 157 { 158 if(cbuffer) 159 _cbuffer = cbuffer; 160 } 161 162 /*void setRedis(AppConfig.RedisConf conf) 163 { 164 version(USE_REDIS){ 165 if(conf.enabled == true && conf.host && conf.port) 166 { 167 conRedis.setDefaultHost(conf.host,conf.port,conf.password); 168 } 169 } 170 } 171 172 void setMemcache(AppConfig.MemcacheConf conf) 173 { 174 version(USE_MEMCACHE){ 175 if(conf.enabled == true){ 176 logDebug(conf); 177 auto tmp1 = split(conf.servers,","); 178 auto tmp2 = split(tmp1[0],":"); 179 if(tmp2[0] && tmp2[1]){ 180 conMemcache.setDefaultHost(tmp2[0],tmp2[1].to!ushort); 181 } 182 } 183 } 184 }*/ 185 186 private void initCache(AppConfig.CacheConf config) 187 { 188 _manger.createCache("default" , config.storage , config.args , config.enableL2); 189 } 190 191 private void initSessionStorage(AppConfig.SessionConf config) 192 { 193 _sessionStorage = new SessionStorage(UCache.CreateUCache(config.storage , config.args , false)); 194 195 _sessionStorage.setPrefix(config.prefix); 196 _sessionStorage.setExpire(config.expire); 197 } 198 199 CacheManger getCacheManger() 200 { 201 return _manger; 202 } 203 204 SessionStorage getSessionStorage() 205 { 206 return _sessionStorage; 207 } 208 209 UCache getCache() 210 { 211 return _manger.getCache("default"); 212 213 } 214 215 AccessManager getAccessManager() 216 { 217 return _accessManager; 218 } 219 220 /** 221 Start the HTTPServer server , and block current thread. 222 */ 223 void run() 224 { 225 start(); 226 } 227 228 /* 229 void run(Address addr) 230 { 231 Config.app.http.address = addr.toAddrString; 232 Config.app.http.port = addr.toPortString.to!ushort; 233 setConfig(Config.app); 234 start(); 235 }*/ 236 237 void setConfig(AppConfig config) 238 { 239 setLogConfig(config.log); 240 upConfig(config); 241 //setRedis(config.redis); 242 //setMemcache(config.memcache); 243 initCache(config.cache); 244 initSessionStorage(config.session); 245 } 246 247 void start() 248 { 249 writeln("Try to open http://",addr.toString(),"/"); 250 _server.start(); 251 } 252 253 /** 254 Stop the server. 255 */ 256 void stop() 257 { 258 _server.stop(); 259 } 260 private: 261 RequestHandler newHandler(RequestHandler, HTTPMessage msg){ 262 if(!msg.upgraded) 263 { 264 return new Request(_cbuffer,&handleRequest,_maxBodySize); 265 } 266 else if(_wfactory) 267 { 268 return _wfactory.newWebSocket(msg); 269 } 270 271 return null; 272 } 273 274 Buffer defaultBuffer(HTTPMessage msg) nothrow 275 { 276 try{ 277 import std.experimental.allocator.gc_allocator; 278 import kiss.container.ByteBuffer; 279 if(msg.chunked == false) 280 { 281 string contign = msg.getHeaders.getSingleOrEmpty(HTTPHeaderCode.CONTENT_LENGTH); 282 if(contign.length > 0) 283 { 284 import std.conv; 285 uint len = 0; 286 collectException(to!(uint)(contign),len); 287 if(len > _maxBodySize) 288 return null; 289 } 290 } 291 292 return new ByteBuffer!(GCAllocator)(); 293 } 294 catch(Exception e) 295 { 296 showException(e); 297 return null; 298 } 299 } 300 301 void handleRequest(Request req) nothrow 302 { 303 this._dispatcher.dispatch(req); 304 } 305 306 private: 307 void upConfig(AppConfig conf) 308 { 309 _maxBodySize = conf.upload.maxSize; 310 version(NO_TASKPOOL) 311 { 312 // NOTHING 313 } 314 else 315 { 316 _tpool = new TaskPool(conf.http.workerThreads); 317 _tpool.isDaemon = true; 318 } 319 320 HTTPServerOptions option = new HTTPServerOptions(); 321 option.maxHeaderSize = conf.http.maxHeaderSize; 322 //option.listenBacklog = conf.http.listenBacklog; 323 324 version(NO_TASKPOOL) 325 { 326 option.threads = conf.http.ioThreads + conf.http.workerThreads; 327 } 328 else 329 { 330 option.threads = conf.http.ioThreads; 331 } 332 333 option.timeOut = conf.http.keepAliveTimeOut; 334 option.handlerFactories ~= (&newHandler); 335 _server = new HttpServer(option); 336 logDebug("addr:",conf.http.address, ":", conf.http.port); 337 addr = parseAddress(conf.http.address,conf.http.port); 338 HTTPServerOptions.IPConfig ipconf; 339 ipconf.address = addr; 340 341 _server.addBind(ipconf); 342 343 //if(conf.webSocketFactory) 344 // _wfactory = conf.webSocketFactory; 345 346 logDebug(conf.route.groups); 347 348 version(NO_TASKPOOL) 349 { 350 } 351 else 352 { 353 this._dispatcher.setWorkers(_tpool); 354 } 355 // init dispatcer and routes 356 if (conf.route.groups) 357 { 358 import std.array : split; 359 import std.string : strip; 360 361 string[] groupConfig; 362 363 foreach (v; split(conf.route.groups, ',')) 364 { 365 groupConfig = split(v, ":"); 366 367 if (groupConfig.length == 3 || groupConfig.length == 4) 368 { 369 string value = groupConfig[2]; 370 371 if (groupConfig.length == 4) 372 { 373 if (std.conv.to!int(groupConfig[3]) > 0) 374 { 375 value ~= ":"~groupConfig[3]; 376 } 377 } 378 379 this._dispatcher.addRouteGroup(strip(groupConfig[0]), strip(groupConfig[1]), strip(value)); 380 381 continue; 382 } 383 384 logWarningf("Group config format error ( %s ).", v); 385 } 386 } 387 388 this._dispatcher.loadRouteGroups(); 389 } 390 391 void setLogConfig(ref AppConfig.LogConfig conf) 392 { 393 int level = 0; 394 switch(conf.level) 395 { 396 case "all": 397 case "trace": 398 case "debug": 399 level = 0; 400 break; 401 case "critical": 402 case "error": 403 level = 3; 404 break; 405 case "fatal": 406 level = 4; 407 break; 408 case "info": 409 level = 1; 410 break; 411 case "warning": 412 level = 2; 413 break; 414 case "off": 415 level = 5; 416 break; 417 default: 418 level = 0; 419 } 420 LogConf logconf; 421 logconf.level = cast(LogLevel)level; 422 logconf.disableConsole = conf.disableConsole; 423 if(!conf.file.empty) 424 logconf.fileName = buildPath(conf.path, conf.file); 425 logconf.maxSize = conf.maxSize; 426 logconf.maxNum = conf.maxNum; 427 428 logLoadConf(logconf); 429 430 } 431 432 433 434 435 version(USE_KISS_RPC) { 436 import kissrpc.RpcManager; 437 public void startRpcService(T,A...)() { 438 if (Config.app.rpc.enabled == false) 439 return; 440 string ip = Config.app.rpc.service.address; 441 ushort port = Config.app.rpc.service.port; 442 int threadNum = Config.app.rpc.service.workerThreads; 443 RpcManager.getInstance().startService!(T,A)(ip, port, threadNum); 444 } 445 public void startRpcClient(T)(string ip, ushort port, int threadNum = 1) { 446 if (Config.app.rpc.enabled == false) 447 return; 448 RpcManager.getInstance().connectService!(T)(ip, port, threadNum); 449 } 450 } 451 452 this() 453 { 454 _cbuffer = &defaultBuffer; 455 _accessManager = new AccessManager(); 456 _manger = new CacheManger(); 457 458 this._dispatcher = new Dispatcher(); 459 setConfig(Config.app); 460 } 461 462 __gshared static Application _app; 463 464 private: 465 Address addr; 466 HttpServer _server; 467 WebSocketFactory _wfactory; 468 uint _maxBodySize; 469 CreatorBuffer _cbuffer; 470 Dispatcher _dispatcher; 471 CacheManger _manger; 472 SessionStorage _sessionStorage; 473 AccessManager _accessManager; 474 475 version(NO_TASKPOOL) 476 { 477 // NOTHING TODO 478 } 479 else 480 { 481 __gshared TaskPool _tpool; 482 } 483 }